﻿#include "hzpch.h"
#include "EditorCamera.h"

#include <glm/ext/quaternion_transform.hpp>

#include "SourceCode/Base/Input.h"
#include "SourceCode/Base/KeyCodes.h"
#include "SourceCode/Base/MouseButtonCodes.h"
#include "SourceCode/Events/MouseEvent.h"

namespace Hazal
{
	CEditorCamera::CEditorCamera(float fFov, float aspectRatio, float fNearClip, float fFarClip)
		: m_fFov(fFov), m_fAspectRatio(aspectRatio), m_fNearClip(fNearClip), m_fFarClip(fFarClip),CCamera(glm::perspective(glm::radians(fFov), aspectRatio, fNearClip, fFarClip))
	{
		
	}

	void CEditorCamera::OnUpdate(float fTimeSpan)
	{
		if (CInput::IsKeyPressed(HZ_KEY_LEFT_ALT))
		{
			const glm::vec2& mouse{CInput::GetMouseX(), CInput::GetMouseY()};

			glm::vec2 vecDelta = (mouse - m_vecInitialMousePosition) * 0.003f;
			m_vecInitialMousePosition = mouse;

			if (CInput::IsMouseButtonPressed(HZ_MOUSE_BUTTON_MIDDLE))
			{
				MousePan(vecDelta);
			}
			else if (CInput::IsMouseButtonPressed(HZ_MOUSE_BUTTON_LEFT))
			{
				MouseRotate(vecDelta);
			}
			else if (CInput::IsMouseButtonPressed(HZ_MOUSE_BUTTON_RIGHT))
			{
				MouseZoom(vecDelta.y * 0.1);
			}
		}

		UpdateView();
	}

	void CEditorCamera::OnEvent(CEvent& event)
	{
		CEventDispatcher dispatcher(event);
		dispatcher.Dispatch<CMouseScrolledEvent>(HZ_BIND_EVENT_FN(CEditorCamera::OnMouseScroll));
	}

	bool CEditorCamera::OnMouseScroll(CMouseScrolledEvent& event)
	{
		float fDelta = event.GetYOffset() * 0.01f;
		MouseZoom(fDelta);

		UpdateView();

		return false;
	}

	void CEditorCamera::MousePan(const glm::vec2& vecDelta)
	{
		auto [xSpeed, ySpeed] = PanSpeed();
		m_vecFocalPoint -= GetRightDirection() * vecDelta.x * xSpeed * m_fDistance;
		m_vecFocalPoint += GetUpDirection() * vecDelta.y * ySpeed * m_fDistance;
	}

	void CEditorCamera::MouseRotate(const glm::vec2& vecDelta)
	{
		float fYawSign = GetUpDirection().y < 0 ? -1.0f : 1.0f;

		m_fYaw += fYawSign * vecDelta.x * RotationSpeed();
		m_fPitch += vecDelta.y * RotationSpeed();
	}

	void CEditorCamera::MouseZoom(float vecDelta)
	{
		m_fDistance -= vecDelta * ZoomSpeed();
		if (m_fDistance < 1.0f)
		{
			m_vecFocalPoint += GetForwardDirection();
			m_fDistance = 1.0f;
		}
	}

	glm::vec3 CEditorCamera::CalculatePosition() const
	{
		return m_vecFocalPoint - GetForwardDirection() * m_fDistance;
	}

	glm::vec3 CEditorCamera::GetUpDirection() const
	{
		glm::vec3 vecRes = glm::mat4_cast(GetOrientation()) * glm::vec4{ 0.0f, 1.0f, 0.0f, 0.0f };
		return glm::normalize(vecRes);
	}

	glm::vec3 CEditorCamera::GetRightDirection() const
	{
		glm::vec3 vecRes = glm::mat4_cast(GetOrientation()) * glm::vec4{ 1.0f, 0.0f, 0.0f, 0.0f };
		return glm::normalize(vecRes);
	}

	glm::vec3 CEditorCamera::GetForwardDirection() const
	{
		glm::vec3 vecRes = glm::mat4_cast(GetOrientation()) * glm::vec4{ 0.0f, 0.0f, -1.0f, 0.0f };
		return glm::normalize(vecRes);
	}

	glm::quat CEditorCamera::GetOrientation() const
	{
		/// Build a quaternion from euler angles (pitch, yaw, roll), in radians.
		return glm::quat(glm::vec3(-m_fPitch, -m_fYaw, 0.0f));
	}

	void CEditorCamera::UpdateProjection()
	{
		m_fAspectRatio = m_fWidth / m_fHeight;
		m_matProjection = glm::perspective(glm::radians(m_fFov), m_fAspectRatio, m_fNearClip, m_fFarClip);
	}

	void CEditorCamera::UpdateView()
	{
		m_vecPosition = CalculatePosition();

		glm::quat oritentation = GetOrientation();
		m_matViewMatrix = glm::translate(glm::mat4(1.0f), m_vecPosition) * glm::mat4_cast(oritentation);
		m_matViewMatrix = glm::inverse(m_matViewMatrix);
	}

	std::pair<float, float> CEditorCamera::PanSpeed() const
	{
		float fX = std::min(m_fWidth / 1000.0f, 2.4f);
		float xFactor = 0.0366f * (fX * fX) - 0.1778f * fX + 0.3021f;

		float fY = std::min(m_fHeight / 1000.0f, 2.4f);
		float yFactor = 0.0366f * (fY * fY) - 0.1778f * fY + 0.3021f;

		return {xFactor, yFactor};
	}

	float CEditorCamera::RotationSpeed() const
	{
		return 0.8f;
	}

	float CEditorCamera::ZoomSpeed() const
	{
		float fDistance = m_fDistance * 0.2f;
		fDistance = std::max(fDistance, 0.0f);

		float fSpeed = fDistance * fDistance;
		fSpeed = std::max(fSpeed, 100.0f);

		return fSpeed;
	}

}